const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const pdfParse = require('pdf-parse');
// const { encode } = require('gpt-3-encoder');

const configurations = require('../../config');
const httpService = require('../http');
const wsPool = require('../../service/ws/connection_pool');

const wsMsgWrapper = (ws, msg, addon) => {
  ws.send(JSON.stringify({
    content: msg,
    ...addon
  }));
}

const fileStorePath = (type, creater, fileName) => {
  const storePath = path.resolve(
    __dirname, '../', '../', configurations.common.StorePath,
    `${creater}`, `${type}`);

  mkdirsSync(storePath);

  return path.resolve(storePath, `${fileName}`);
};

const digest = (fileContent) => {
  try {
    const SHA256 = crypto.createHash('sha256');
    SHA256.update(fileContent);
    return SHA256.digest('base64');
  } catch (error) {
    throw `generate file digest error: ${error}`;
  }
};

const writeFile = async (filePath, content) => {
  mkdirsSync(path.resolve(filePath, '../'));
  fs.writeFileSync(filePath, content);
};

const delDir = async (dirPath) => {
  if (fs.existsSync(dirPath)) {
    fs.rmSync(dirPath, { recursive: true })
  }
};

const clearTempDir = async (creater) => {
  const tempDirPath = path.resolve(
    configurations.common.uploadPath,
    `${creater}`
  );

  if (fs.existsSync(tempDirPath)) {
    fs.rmSync(tempDirPath, { recursive: true })
  }
};

const mkdirsSync = (dirPath) => {
  if (fs.existsSync(dirPath)) {
    return true;
  } else {
    const parentDir = path.resolve(dirPath, '..');
    if (mkdirsSync(parentDir)) {
      fs.mkdirSync(dirPath);
      return true;
    }
  }
};

const parseContract = async (path, ws_client_id) => {
  try {
    const ws = wsPool.get(ws_client_id);
    wsMsgWrapper(ws, `starting parse contract ... `);

    const dataBuffer = fs.readFileSync(path);
    const data = await pdfParse(dataBuffer);

    wsMsgWrapper(ws, `complete pdf parse`);
    wsMsgWrapper(ws, `start pdf content analysis ...`);

    wsMsgWrapper(ws, `start clear pdf content ...`);
    const text = pureContent(data.text);
    wsMsgWrapper(ws, `complete pdf content clean work`);

    // console.log(`PDF 内容：${text}`);

    const aiAnalysisResult = await aiParse(ws, text);

    return aiAnalysisResult;

  } catch (error) {
    console.error("读取 PDF 文件失败: ", error);
    throw error;
  };
}

const pureContent = (content) => {
  if (!content) return '';

  let cleanText = content
    // 移除HTML标签
    .replace(/<[^>]*>/g, '')

    // 移除XML标签
    .replace(/<\?.*?\?>/g, '')
    .replace(/<!\[CDATA\[.*?\]\]>/g, '')

    // 移除特殊标记
    .replace(/\[.*?\]/g, '')
    .replace(/\{.*?\}/g, '')

    // 移除控制字符和特殊字符
    .replace(/[\x00-\x1F\x7F-\x9F]/g, '')

    // 替换Windows和Unix风格的换行符为空格
    .replace(/\r\n|\r|\n/g, ' ')

    // 移除PDF特殊标记
    .replace(/Form\d+/g, '')
    .replace(/\f/g, '')

    // 移除特殊标点符号组合
    .replace(/[–—]/g, '-')

    // 移除页码标记
    .replace(/Page \d+ of \d+/g, '')

    // 移除连续的标点符号
    .replace(/[.]{2,}/g, '.')
    .replace(/[,]{2,}/g, ',')

    // 移除Unicode控制字符
    .replace(/[\u0000-\u001F\u007F-\u009F]/g, '')

    // 最后清理：移除开头和结尾的空白字符
    .trim();

  return cleanText;
};

const aiParse = async (ws, text) => {
  // const ratio = 0.85;
  // const endIndex = Math.floor(text.length * ratio);
  // const prompts = assembleContractParsePrompts(text.slice(0, endIndex));  // 100742

  if (text.length > configurations.common.DEEPSEEK_REQUEST_BODY_MAX_LENGTH) {
    const percentage = (configurations.common.DEEPSEEK_REQUEST_BODY_MAX_LENGTH / text.length) * 100;
    wsMsgWrapper(ws, `original contract content length is: ${text.length}, for current version we can only handle previous ${percentage.toFixed(2)}% length content`);
    text = text.slice(0, configurations.common.DEEPSEEK_REQUEST_BODY_MAX_LENGTH);
  }

  const prompts = assembleContractParsePrompts(text);

  const url = `${configurations.common.DEEPSEEK_BASE_URL}${configurations.common.DEEPSEEK_API_CHAT_COMPLETIONS}`

  const data = {
    "messages": [
      {
        "content": "You are a helpful assistant that analyzes contracts and extracts key information. Always return response in valid JSON format.",
        "role": "system"
      },
      {
        "content": prompts,
        "role": "user"
      },
    ],
    "model": configurations.common.DEEPSEEK_MODEL,
    "temperature": 0.3,
    "max_tokens": 3000
  }


  const headers = {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${configurations.common.DEEPSEEK_API_KEY}`
  }

  console.log(`test length: ${text.length}, and wrapped data length is: ${JSON.stringify(data).length}`);

  const options = {
    method: 'POST',
    url,
    headers,
    data,
  }

  const tokenLength = text.length * 0.6; // encode(text).length;
  const estimateTimeSpend = tokenLength / configurations.common.DEEPSEEK_PROGRESS_SPEED;

  wsMsgWrapper(ws, `Start analyzing contract content by AI ...`, { estimateTimeSpend });
  // wsMsgWrapper(ws, `sending request options: ${JSON.stringify(options)}`);


  const startTime = Date.now();
  const resp = await httpService.invokeHttpRequest(options);
  const endTime = Date.now();
  console.log(`============  ai invoke Deepseek response: ${resp.status} ${JSON.stringify(resp.data)}`);
  wsMsgWrapper(ws, `AI analysis of contract content completed, takes ${(endTime - startTime)/1000} seconds`);

  wsMsgWrapper(ws, `Start assembleing contract analysis result ...`);
  const parseResult = assembleResult(ws, resp);
  wsMsgWrapper(ws, `complete assemble result: ${JSON.stringify(parseResult)}`);

  return parseResult;
}

const assembleContractParsePrompts = (text) => {
  const prompts = "" +
    "Please analyze the following contract content and extract key information as a single JSON object. " +
    `Contract content: ${text} ` +
    "For payment milestone information in the contract, extract the data according to these fixed columns: " +
    "'Invoice Month Expected', 'Payment Milestone No', 'Description', '% for each', 'K USD for each', '% Payment (Cumulative)', 'Check List', 'Forecast payment time'. " +
    "Leave the 'Forecast payment time' field empty for all rows. " +
    "Return the result directly as a JSON object in the following format:" +
    "```json" +
    "{" +
    '    "contract_amount": "amount in numbers",\n' +
    '    "party_a": "party A name",\n' +
    '    "party_b": "party B name",\n' +
    '    "signing_date": "YYYY-MM-DD",\n' +
    '    "contract_id": "contract ID or contract number",\n' +
    '    "contract_name": "contract name",\n' +
    '    "milestone": {\n' +
    '         "columns": ["Invoice Month Expected", "Payment Milestone No", "Description", "% for each", "K USD for each", "% Payment (Cumulative)", "Check List", "Forecast payment time"],\n' +
    '         "dataSource": [\n' +
    '             // rows with data for each column (except "Forecast payment time" which should be empty)\n' +
    '         ]\n' +
    '    }\n' +
    "}" +
    "```";

  return prompts;
}


const assembleResult = (ws, data) => {
  const { content } = data.choices[0].message;

  let pureResult = content.replace(/<think>[\s\S]*?<\/think>/g, '');
  pureResult = pureResult.replace(/```json([\s\S]*?)```/g, '$1');
  pureResult = pureResult.replace(/(?<!\\)[\r\n\t\f\v\b\0\x00-\x1F\x7F-\x9F]/g, ' ');

  console.log(`pureResult: ${pureResult}`);

  try {
    let jsonObj = JSON.parse(pureResult);
    if (Array.isArray(jsonObj)) {
      jsonObj = jsonObj[0];
    }

    wsMsgWrapper(ws, `start calculate forecast payment time ...`);
    jsonObj = calForcastPaymentTime(jsonObj);
    wsMsgWrapper(ws, `complete calculate forecast payment: ${JSON.stringify(jsonObj)}`);

    return { data: jsonObj };
  } catch (error) {
    wsMsgWrapper(ws, `assembleResult meet error: ${error} from data: ${JSON.stringify(data)}`);
    console.error(`assembleResult meet error: ${error} from data: ${JSON.stringify(data)}`);
    return { data: pureResult };
  }
}

const calForcastPaymentTime = (obj) => {
  if (!obj || !obj.milestone || !Array.isArray(obj.milestone.dataSource)) {
    return obj;
  }

  // Parse the signing date (format: YYYY-MM-DD)
  const signingDate = obj.signing_date;
  if (!signingDate || typeof signingDate !== 'string') {
    return obj;
  }

  // Extract signing date month and year
  const signingMatch = signingDate.match(/^(\d{4})-(\d{2})/);
  if (!signingMatch) {
    return obj;
  }

  const signingYear = parseInt(signingMatch[1], 10);
  const signingMonth = parseInt(signingMatch[2], 10);

  const { columns, dataSource } = obj.milestone;

  const tableColumns = columns.map(c => {
    return {
      title: c,
      dataIndex: c,
      key: c,
    }
  });

  console.log(`tableColumns: ${JSON.stringify(tableColumns)}`);

  const tableDataSource = dataSource.map((r, i) => {
    console.log(`start assembling dataSource[${`i`}]: ${JSON.stringify(r)}`);
    const row = {
      key: i,
    }

    for (let i = 0; i < columns.length; i++) {
      row[tableColumns[i].key] = r[i];
    }

    const invoiceMonthStr = r[0]; //["Invoice Month Expected"] || "";
    const monthMatch = invoiceMonthStr.match(/End of Month (\d{2})/i);

    console.log(`monthMatch: ${monthMatch}`);

    console.log(`start calculate forecast payment time ...`);
    let forecastDate = "";

    if (monthMatch && monthMatch[1]) {
      console.log(`forecast payment time can be calculated ...`);
      const invoiceMonth = parseInt(monthMatch[1], 10);

      // Calculate target date (signing month + invoice month - 1)
      let targetMonth = signingMonth + invoiceMonth - 1;
      let targetYear = signingYear;

      // Adjust year if month exceeds 12
      while (targetMonth > 12) {
        targetYear += 1;
        targetMonth -= 12;
      }

      // Format as YYYYMM (without hyphen)
      forecastDate = `${targetYear}${String(targetMonth).padStart(2, '0')}`;
      console.log(`forecast payment time is: ${forecastDate}`);
    }

    row['Forecast payment time'] = forecastDate;

    return row;
  });

  obj.milestone.dataSource = tableDataSource;
  obj.milestone.columns = tableColumns;

  return obj;
};

const fileService = {
  writeFile,
  delDir,
  clearTempDir,
  fileStorePath,
  mkdirsSync,
  parseContract,
  digest,
};

module.exports = fileService;
